﻿@page "/ssrsviewer"
@page "/docs/guides/components/ssrsviewer.html"

<RadzenText TextStyle="TextStyle.H2" TagName="TagName.H1" class="rz-pt-8">
    SSRS Viewer
</RadzenText>
<RadzenText TextStyle="TextStyle.Subtitle1" TagName="TagName.P" class="rz-pb-4">
    RadzenSSRSViewer displays a report created in SQL Server Reporting Services (SSRS).
</RadzenText>

<RadzenText TextStyle="TextStyle.Subtitle2" TagName="TagName.P" class="rz-pb-4">
    This documentation covers SSRS report display with required <code>ReportName</code> and <code>ReportServer</code> properties, parameter passing via <code>Parameters</code> collection, proxy support with <code>UseProxy</code> for secured/internal report servers, and credential configuration using <code>OnHttpClientHandlerCreate</code> partial method or direct <code>HttpClientHandler.Credentials</code> setup.
</RadzenText>

<RadzenAlert AlertStyle="AlertStyle.Info" Variant="Variant.Flat" Shade="Shade.Lighter" AllowClose="false" class="rz-mb-12">
    There is no live demo of RadzenSSRSViewer because hosting a public SSRS server is not a trivial task.
</RadzenAlert>
<RadzenText TextStyle="TextStyle.Body1" class="rz-mb-8">
To display a report you should specify:
</RadzenText>
<ol>
    <li>The name of the report via the ReportName property.</li>
    <li>The URL of your report server via the ReportServer property.</li>
</ol>
Here is a minimal example:
<pre>
<code>
&lt;RadzenSSRSViewer ReportName="Untitled" ReportServer="http://myserver/ReportServer/" /&gt;
</code>
</pre>
<RadzenText Anchor="ssrsviewer#parameters" TextStyle="TextStyle.H5" TagName="TagName.H2" class="rz-mt-12 rz-mb-4">Parameters</RadzenText>
<RadzenText TextStyle="TextStyle.Body1" class="rz-mb-8">
Often SSRS reports have parameters. You can specify those via the <code>Parameters</code> collection:
</RadzenText>
<pre>
<code>
&lt;RadzenSSRSViewer ReportName="Untitled" ReportServer="http://myserver/ReportServer/"&gt;
  &lt;Parameters&gt;
    &lt;RadzenSSRSViewerParameter ParameterName="Param1" Value="1" /&gt;
    &lt;RadzenSSRSViewerParameter ParameterName="Param2" Value="2" /&gt;
  &lt;/Parameters&gt;
&lt;/RadzenSSRSViewer&gt;
</code>
</pre>
<RadzenText Anchor="ssrsviewer#proxy" TextStyle="TextStyle.H5" TagName="TagName.H2" class="rz-mt-12 rz-mb-4">Proxy</RadzenText>
<RadzenText TextStyle="TextStyle.Body1" class="rz-mb-8">
Often your report server won't be exposed to the public Internet or you would want to either hide report parameters or provide security credentials. In this case you can use the built-in proxy support in RadzenSSRSViewer.
To enable it set the <code>UseProxy</code> property to <code>true</code>and add the <code>ReportController</code> class below to your Blazor application.
</RadzenText>
<RadzenTabs>
    <Tabs>
        <RadzenTabsItem Text="Page">
<pre>
<code>
&lt;RadzenSSRSViewer UseProxy="true" ReportName="Untitled" ReportServer="http://myserver/ReportServer/"&gt;
  &lt;Parameters&gt;
    &lt;RadzenSSRSViewerParameter ParameterName="Param1" Value="1" /&gt;
    &lt;RadzenSSRSViewerParameter ParameterName="Param2" Value="2" /&gt;
  &lt;/Parameters&gt;
&lt;/RadzenSSRSViewer&gt;
</code>
</pre>
        </RadzenTabsItem>
        <RadzenTabsItem Text="ReportController">
<pre>
<code>
using System.Net;
using System.Text;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;

public namespace YourAppNamespace.Controllers
{
    public partial class ReportController : Controller
    {
        [HttpGet("/__ssrsreport")]
        public async Task Get(string url)
        {
            if (!String.IsNullOrEmpty(url))
            {
                using (var httpClient = CreateHttpClient())
                {
                    var responseMessage = await ForwardRequest(httpClient, Request, url);

                    CopyResponseHeaders(responseMessage, Response);

                    await WriteResponse(Request, url, responseMessage, Response, false);
                }
            }
        }

        [Route("/ssrsproxy/{*url}")]
        public async Task Proxy()
        {
            var urlToReplace = String.Format("{0}://{1}{2}/{3}/", Request.Scheme, Request.Host.Value, Request.PathBase, "ssrsproxy");
            var requestedUrl = Request.GetDisplayUrl().Replace(urlToReplace, "", StringComparison.InvariantCultureIgnoreCase);
            var reportServerIndex = requestedUrl.IndexOf("/ReportServer", StringComparison.InvariantCultureIgnoreCase);
            if (reportServerIndex == -1)
            {
                reportServerIndex = requestedUrl.IndexOf("/Reports", StringComparison.InvariantCultureIgnoreCase);
            }
            var reportUrlParts = requestedUrl.Substring(0, reportServerIndex).Split('/');

            var url = String.Format("{0}://{1}:{2}{3}", reportUrlParts[0], reportUrlParts[1], reportUrlParts[2],
                requestedUrl.Substring(reportServerIndex, requestedUrl.Length - reportServerIndex));

            using (var httpClient = CreateHttpClient())
            {
                var responseMessage = await ForwardRequest(httpClient, Request, url);

                CopyResponseHeaders(responseMessage, Response);

                if (Request.Method == "POST")
                {
                    await WriteResponse(Request, url, responseMessage, Response, true);
                }
                else
                {
                    if (responseMessage.Content.Headers.ContentType != null && responseMessage.Content.Headers.ContentType.MediaType == "text/html")
                    {
                        await WriteResponse(Request, url, responseMessage, Response, false);
                    }
                    else
                    {
                        using (var responseStream = await responseMessage.Content.ReadAsStreamAsync())
                        {
                            await responseStream.CopyToAsync(Response.Body, 81920, HttpContext.RequestAborted);
                        }
                    }
                }
            }
        }

        partial void OnHttpClientHandlerCreate(ref HttpClientHandler handler);

        private HttpClient CreateHttpClient()
        {
            var httpClientHandler = new HttpClientHandler();

            httpClientHandler.AllowAutoRedirect = true;
            httpClientHandler.UseDefaultCredentials = true;

            if (httpClientHandler.SupportsAutomaticDecompression)
            {
                httpClientHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
            }

            OnHttpClientHandlerCreate(ref httpClientHandler);

            return new HttpClient(httpClientHandler);
        }

        partial void OnReportRequest(ref HttpRequestMessage requestMessage);

        async Task&lt;HttpResponseMessage&gt; ForwardRequest(HttpClient httpClient, HttpRequest currentReqest, string url)
        {
            var proxyRequestMessage = new HttpRequestMessage(new HttpMethod(currentReqest.Method), url);

            foreach (var header in currentReqest.Headers)
            {
                if (header.Key != "Host")
                {
                    proxyRequestMessage.Headers.TryAddWithoutValidation(header.Key, new string[] { header.Value });
                }
            }

            this.OnReportRequest(ref proxyRequestMessage);

            if (currentReqest.Method == "POST")
            {
                using (var stream = new MemoryStream())
                {
                    await currentReqest.Body.CopyToAsync(stream);
                    stream.Position = 0;

                    string body = new StreamReader(stream).ReadToEnd();
                    proxyRequestMessage.Content = new StringContent(body);

                    if (body.IndexOf("AjaxScriptManager") != -1)
                    {
                        proxyRequestMessage.Content.Headers.Remove("Content-Type");
                        proxyRequestMessage.Content.Headers.Add("Content-Type", new string[] { currentReqest.ContentType });
                    }
                }
            }

            return await httpClient.SendAsync(proxyRequestMessage);
        }

        static void CopyResponseHeaders(HttpResponseMessage responseMessage, HttpResponse response)
        {
            response.StatusCode = (int)responseMessage.StatusCode;
            foreach (var header in responseMessage.Headers)
            {
                response.Headers[header.Key] = header.Value.ToArray();
            }

            foreach (var header in responseMessage.Content.Headers)
            {
                response.Headers[header.Key] = header.Value.ToArray();
            }

            response.Headers.Remove("transfer-encoding");
        }

        static async Task WriteResponse(HttpRequest currentReqest, string url, HttpResponseMessage responseMessage, HttpResponse response, bool isAjax)
        {
            var result = await responseMessage.Content.ReadAsStringAsync();

            var ReportServer = url.Contains("/ReportServer/", StringComparison.InvariantCultureIgnoreCase) ? "ReportServer" : "Reports";

            var reportUri = new Uri(url);
            var proxyUrl = String.Format("{0}://{1}{2}/ssrsproxy/{3}/{4}/{5}", currentReqest.Scheme, currentReqest.Host.Value, currentReqest.PathBase,
                reportUri.Scheme, reportUri.Host, reportUri.Port);

            if (isAjax && result.IndexOf("|") != -1)
            {
                var builder = new StringBuilder();

                var delimiterIndex = 0;
                var length = 0;
                var index = 0;

                var type = "";
                var id = "";
                var content = "";

                while (index &lt; result.Length)
                {
                    delimiterIndex = result.IndexOf("|", index);
                    if (delimiterIndex == -1)
                    {
                        break;
                    }
                    length = int.Parse(result.Substring(index, delimiterIndex - index));
                    if ((length % 1) != 0)
                    {
                        break;
                    }
                    index = delimiterIndex + 1;
                    delimiterIndex = result.IndexOf("|", index);
                    if (delimiterIndex == -1)
                    {
                        break;
                    }
                    type = result.Substring(index, delimiterIndex - index);
                    index = delimiterIndex + 1;
                    delimiterIndex = result.IndexOf("|", index);
                    if (delimiterIndex == -1)
                    {
                        break;
                    }
                    id = result.Substring(index, delimiterIndex - index);
                    index = delimiterIndex + 1;
                    if ((index + length) &gt;= result.Length)
                    {
                        break;
                    }
                    content = result.Substring(index, length);
                    index += length;
                    if (result.Substring(index, 1) != "|")
                    {
                        break;
                    }
                    index++;

                    content = content.Replace($"/{ReportServer}/", $"{proxyUrl}/{ReportServer}/", StringComparison.InvariantCultureIgnoreCase);
                    if (content.Contains("./ReportViewer.aspx", StringComparison.InvariantCultureIgnoreCase))
                    {
                        content = content.Replace("./ReportViewer.aspx", $"{proxyUrl}/{ReportServer}/Pages/ReportViewer.aspx", StringComparison.InvariantCultureIgnoreCase);
                    }
                    else
                    {
                        content = content.Replace("ReportViewer.aspx", $"{proxyUrl}/{ReportServer}/Pages/ReportViewer.aspx", StringComparison.InvariantCultureIgnoreCase);
                    }

                    builder.Append(String.Format("{0}|{1}|{2}|{3}|", content.Length, type, id, content));
                }

                result = builder.ToString();
            }
            else
            {
                result = result.Replace($"/{ReportServer}/", $"{proxyUrl}/{ReportServer}/", StringComparison.InvariantCultureIgnoreCase);

                if (result.Contains("./ReportViewer.aspx", StringComparison.InvariantCultureIgnoreCase))
                {
                    result = result.Replace("./ReportViewer.aspx", $"{proxyUrl}/{ReportServer}/Pages/ReportViewer.aspx", StringComparison.InvariantCultureIgnoreCase);
                }
                else
                {
                    result = result.Replace("ReportViewer.aspx", $"{proxyUrl}/{ReportServer}/Pages/ReportViewer.aspx", StringComparison.InvariantCultureIgnoreCase);
                }
            }

            response.Headers.Remove("Content-Length");
            response.Headers.Append("Content-Length", new string[] { System.Text.Encoding.UTF8.GetByteCount(result).ToString() });

            await response.WriteAsync(result);
        }
    }
}
</code>
</pre>
        </RadzenTabsItem>
    </Tabs>
</RadzenTabs>
<RadzenText Anchor="ssrsviewer#credentials" TextStyle="TextStyle.H5" TagName="TagName.H2" class="rz-mt-12 rz-mb-4">Provide credentials</RadzenText>
<RadzenText TextStyle="TextStyle.Body1" class="rz-mb-8">
    To provide user credentials when making the proxy requests you can implement the OnHttpClientHandlerCreate partial method of ReportController.
</RadzenText>
<ol>
    <li>Add a new file e.g. <code>ReportController.Credentials.cs</code></li>
    <li>Implement the <code>OnHttpClientHandlerCreate</code> partial method:
<pre>
<code>
using System.Net;
using System.Text;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
public namespace YourAppNamespace.Controllers
{
   public partial class ReportController
   {
       void OnHttpClientHandlerCreate(ref httpClientHandler);
       {
         httpClientHandler.Credentials = new NetworkCredential("username", "password", "domain");
       }
   }
}
</code>
</pre>
     </li>
</ol>
<RadzenText TextStyle="TextStyle.Body1" class="rz-mb-8">
    Alternatively you can set the <code>Credentials</code> property of <code>httpClientHandler</code> directly in the <code>CreateHttpClient</code> method of the
    <code>ReportController</code> class:
</RadzenText>
<pre>
<code>
private HttpClient CreateHttpClient()
{
    var httpClientHandler = new HttpClientHandler();

    httpClientHandler.AllowAutoRedirect = true;
    httpClientHandler.UseDefaultCredentials = true;

    // Set the credentials
    httpClientHandler.Credentials = new NetworkCredential("username", "password", "domain");

    if (httpClientHandler.SupportsAutomaticDecompression)
    {
        httpClientHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
    }

    OnHttpClientHandlerCreate(ref httpClientHandler);

    return new HttpClient(httpClientHandler);
}
</code>
</pre>
<RadzenText TextStyle="TextStyle.Body1" class="rz-mb-8">
    In some setups authenticating the request like this could fail with exceptions such as:
</RadzenText>
<ul>
    <li><code>HttpRequestException: Authentication failed because the connection could not be reused.</code>. If this happens check <a href="https://forum.radzen.com/t/radzenssrsviewer-authentication-failed-because-the-connection-could-not-be-reused/3065/4">this forum thread</a></li>
    <li><code>System.ComponentModel.Win32Exception (0x80090302): The function requested is not supported</code>. If this happens check <a href="https://forum.radzen.com/t/passing-authentication-to-ssrs-report-viewer-net-5/6148/8">this forum thread</a></li>
</ul>